﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.SharePoint;

namespace SMIL.Server.PictureLibraryExif
{
    #region MySPList
    public static class MySPList
    {
        public static bool ContainsCoordinateType(this SPList spList)
        {
            try
            {
                foreach (SPField spField in spList.Fields)
                {
                    if (spField.TypeDisplayName.Equals("SMIL.Coordinate"))
                        return true;
                }
                return false;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public static string GetCoordinateFieldName(this SPList spList)
        {
            try
            {
                foreach (SPField spField in spList.Fields)
                {
                    if (spField.TypeDisplayName.Equals("SMIL.Coordinate"))
                        return spField.Title;
                }
                return string.Empty;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
    #endregion

    public class PictureLibraryExif : SPItemEventReceiver
    {        
        public override void ItemAdded(SPItemEventProperties properties)
        {
            if (!properties.ListItem.ParentList.ContainsCoordinateType())
                return;

            string coordinate_field_name = properties.ListItem.ParentList.GetCoordinateFieldName();

            try
            {
                SPListItem item = properties.ListItem;

                if (Path.GetExtension(item.File.Name).ToLower() == ".jpg" || Path.GetExtension(item.File.Name).ToLower() == ".jpeg")
                {
                    Common.WriteULSVerbose(string.Format("found a jpeg. (Filename={0})", item.File.Name));
                    Stream stream = properties.ListItem.File.OpenBinaryStream();
                    Image image = Image.FromStream(stream);
                    
                    // Parse date
                    DateTime? imageDate = GetImageDate(image);

                    if (imageDate != null)
                    {
                        Common.WriteULSVerbose("setting ImageCreateDate");
                        properties.ListItem[SPBuiltInFieldId.ImageCreateDate] = imageDate;// ((DateTime)imageDate).ToString("MM/dd/yyyy hh:mm tt");

                        this.DisableEventFiring();
                        item.SystemUpdate(false);
                        this.EnableEventFiring();
                    }

                    // Parse GPS-coordinates from EXIF
                    double lon, lat;
                    GetLongLat(image, out lon, out lat);

                    if (lon != 0 && lat != 0)
                    {
                      
                        Common.WriteULSVerbose("setting Longitude/Latitude");

                        properties.ListItem[coordinate_field_name] = "n=" + lat + " e=" + lon + " zoom=2";                    

                        this.DisableEventFiring();
                        item.SystemUpdate(false);
                        this.EnableEventFiring();
                    }
                }
            }
            catch (Exception ex)
            {
                Common.WriteULSError(string.Format("Error obtaining image datestamp: {0}", ex));
            }
        }
     
        private static void GetLongLat(Image image, out double lon, out double lat)
        {
            Common.WriteULSVerbose("Trying to find a GPS longitue/latitude from the exif data");
            int lonIndex = -1;
            int latIndex = -1;
            int lonRefIndex = -1;
            int latRefIndex = -1;

            lon = lat = 0;

            // find the index of the id's we're looking for
            // Note: the Exif specification at http://exif.org specifies 0x2 for
            // the latitude and 0x4 for the longitude
            for (int i = 0; i < image.PropertyIdList.Length; i++)
            {
                if (image.PropertyIdList[i] == Int32.Parse("1", NumberStyles.HexNumber))
                    latRefIndex = i;

                if (image.PropertyIdList[i] == Int32.Parse("2", NumberStyles.HexNumber))
                    latIndex = i;

                if (image.PropertyIdList[i] == Int32.Parse("3", NumberStyles.HexNumber))
                    lonRefIndex = i;

                if (image.PropertyIdList[i] == Int32.Parse("4", NumberStyles.HexNumber))
                    lonIndex = i;

                if (latIndex != -1 && lonIndex != -1 && latRefIndex != -1 && lonRefIndex != -1)
                    break;
            }

            // return if the tags are not found
            if (lonIndex < 0 || latIndex < 0) return;

            try
            {
                PropertyItem lat_item = image.PropertyItems[latIndex];             
                PropertyItem lon_item = image.PropertyItems[lonIndex];
                
                //lat                
                double a = BitConverter.ToUInt32(lat_item.Value, 0);
                double b = BitConverter.ToUInt32(lat_item.Value, 4);
                double c = BitConverter.ToUInt32(lat_item.Value, 8);
                double d = BitConverter.ToUInt32(lat_item.Value, 12);
                double e = BitConverter.ToUInt32(lat_item.Value, 16);
                double f = BitConverter.ToUInt32(lat_item.Value, 20);

                lat = ((a / b) + ((c / d) / 60) + ((e / f) / 3600));

                //long               
                a = BitConverter.ToUInt32(lon_item.Value, 0);
                b = BitConverter.ToUInt32(lon_item.Value, 4);
                c = BitConverter.ToUInt32(lon_item.Value, 8);
                d = BitConverter.ToUInt32(lon_item.Value, 12);
                e = BitConverter.ToUInt32(lon_item.Value, 16);
                f = BitConverter.ToUInt32(lon_item.Value, 20);
                
                lon = ((a / b) + ((c / d) / 60) + ((e / f) / 3600));                

                // Use North/East-reference to apply sign only if available
                if (lonRefIndex != 0 && latRefIndex != 0)
                {
                    PropertyItem latref_item = image.PropertyItems[latRefIndex];                    
                    PropertyItem lonref_item = image.PropertyItems[lonRefIndex];

                    lon = (lonref_item.Value[0] == 'E') ? lon : -lon;
                    lat = (latref_item.Value[0] == 'N') ? lat : -lat;
                }

            }
            catch { }
            // in case of an exception, we'll just ignore it

            return;
        }

        private static DateTime? GetImageDate(Image image)
        {            
            Common.WriteULSVerbose("Trying to find a DateTime from the exif data");
            int tagIndex = -1;

            DateTime? date = null;

            // find the index of the id we're looking for
            // Note: the Exif specification at http://exif.org specifies 0x9003 for
            // the date/time the image was taken
            for (int i = 0; i < image.PropertyIdList.Length; i++)
            {
                if (image.PropertyIdList[i] == Int32.Parse("9003", NumberStyles.HexNumber))
                {
                    tagIndex = i;
                    break;
                }
            }

            // return if the tag is not found
            if (tagIndex < 0) return null;

            // parse the date string which is in the format: yyyy:mm:dd hh:mm:ss
            string dateString;

            try
            {
                PropertyItem item = image.PropertyItems[tagIndex];
                dateString = Encoding.ASCII.GetString(item.Value);
                Common.WriteULSVerbose(string.Format("found a datetime: {0}", dateString));

                string[] dParts = dateString.Split(new[] { ":", " " }, StringSplitOptions.RemoveEmptyEntries);
                int year = Convert.ToInt32(dParts[0].Trim());
                int month = Convert.ToInt32(dParts[1].Trim());
                int day = Convert.ToInt32(dParts[2].Trim());
                int hour = Convert.ToInt32(dParts[3].Trim());
                int minute = Convert.ToInt32(dParts[4].Trim());
                int second = Convert.ToInt32(dParts[5].Trim());

                date = new DateTime(year, month, day, hour, minute, second);

            }
            catch { }
            // in case of an exception, we'll just ignore it and not return a date

            return date;
        }



    }

    
}